# 指定文件编码为UTF-8
# coding: utf-8

"""
multi_layer_net_extend.py 功能说明：
1. 实现扩展版的全连接多层神经网络，支持多种正则化和优化技术
2. 包含Weight Decay(L2正则化)、Dropout和Batch Normalization功能
3. 支持ReLU和Sigmoid两种激活函数
4. 提供He和Xavier两种权重初始化方法
5. 实现数值梯度和反向传播两种梯度计算方法

主要功能：
- 前向传播(predict)
- 损失计算(loss)
- 准确率计算(accuracy)
- 梯度计算(numerical_gradient/gradient)
- 支持多种正则化技术

网络结构：
输入层 -> [Affine -> (BatchNorm) -> Activation -> (Dropout)] x N -> Affine -> SoftmaxWithLoss
"""

# 导入系统模块
import sys, os
# 添加父目录到系统路径
sys.path.append(os.pardir)
# 导入NumPy数值计算库
import numpy as np
# 导入有序字典类
from collections import OrderedDict
# 从common.layers导入网络层
from common.layers import *
# 从common.gradient导入数值梯度计算
from common.gradient import numerical_gradient

class MultiLayerNetExtend:
    """扩展版全连接多层神经网络类

    参数说明:
    ----------
    input_size : 输入层大小(如MNIST为784)
    hidden_size_list : 各隐藏层神经元数量列表(如[100,100,100])
    output_size : 输出层大小(如MNIST为10)
    activation : 激活函数类型('relu'或'sigmoid')
    weight_init_std : 权重初始化标准差
        'relu'或'he' - 使用He初始化
        'sigmoid'或'xavier' - 使用Xavier初始化
    weight_decay_lambda : L2正则化强度系数
    use_dropout: 是否使用Dropout
    dropout_ration : Dropout的比例
    use_batchNorm: 是否使用Batch Normalization
    """
    def __init__(self, input_size, hidden_size_list, output_size,
                 activation='relu', weight_init_std='relu', weight_decay_lambda=0,
                 use_dropout = False, dropout_ration = 0.5, use_batchnorm=False):
        self.input_size = input_size
        self.output_size = output_size
        self.hidden_size_list = hidden_size_list
        self.hidden_layer_num = len(hidden_size_list)  # 隐藏层数量
        self.use_dropout = use_dropout  # 是否使用Dropout
        self.weight_decay_lambda = weight_decay_lambda  # L2正则化系数
        self.use_batchnorm = use_batchnorm  # 是否使用BatchNorm
        self.params = {}  # 存储网络参数

        # 初始化权重
        self.__init_weight(weight_init_std)

        # 构建网络层
        activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}  # 激活函数层字典
        self.layers = OrderedDict()  # 有序字典存储网络层

        # 添加隐藏层
        for idx in range(1, self.hidden_layer_num+1):
            # 全连接层
            self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
                                                    self.params['b' + str(idx)])
            # Batch Normalization层
            if self.use_batchnorm:
                self.params['gamma' + str(idx)] = np.ones(hidden_size_list[idx-1])
                self.params['beta' + str(idx)] = np.zeros(hidden_size_list[idx-1])
                self.layers['BatchNorm' + str(idx)] = BatchNormalization(
                    self.params['gamma' + str(idx)], self.params['beta' + str(idx)])

            # 激活函数层
            self.layers['Activation_function' + str(idx)] = activation_layer[activation]()

            # Dropout层
            if self.use_dropout:
                self.layers['Dropout' + str(idx)] = Dropout(dropout_ration)

        # 输出层
        idx = self.hidden_layer_num + 1
        self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
                                                self.params['b' + str(idx)])

        # 最后一层(SoftmaxWithLoss)
        self.last_layer = SoftmaxWithLoss()

    def __init_weight(self, weight_init_std):
        """初始化权重参数

        参数:
            weight_init_std : 权重初始化方式
        """
        # 构建各层尺寸列表[输入,隐藏层1,...,输出]
        all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size]

        # 初始化各层权重
        for idx in range(1, len(all_size_list)):
            scale = weight_init_std
            # He初始化(配合ReLU)
            if str(weight_init_std).lower() in ('relu', 'he'):
                scale = np.sqrt(2.0 / all_size_list[idx - 1])
            # Xavier初始化(配合Sigmoid)
            elif str(weight_init_std).lower() in ('sigmoid', 'xavier'):
                scale = np.sqrt(1.0 / all_size_list[idx - 1])

            # 初始化权重和偏置
            self.params['W' + str(idx)] = scale * np.random.randn(
                all_size_list[idx-1], all_size_list[idx])
            self.params['b' + str(idx)] = np.zeros(all_size_list[idx])

    def predict(self, x, train_flg=False):
        """前向传播计算输出"""
        for key, layer in self.layers.items():
            # Dropout和BatchNorm区分训练/测试模式
            if "Dropout" in key or "BatchNorm" in key:
                x = layer.forward(x, train_flg)
            else:
                x = layer.forward(x)
        return x

    def loss(self, x, t, train_flg=False):
        """计算损失函数值(含L2正则化)"""
        y = self.predict(x, train_flg)  # 前向传播

        # 计算L2正则化项
        weight_decay = 0
        for idx in range(1, self.hidden_layer_num + 2):
            W = self.params['W' + str(idx)]
            weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W**2)

        # 交叉熵损失 + L2正则化
        return self.last_layer.forward(y, t) + weight_decay

    def accuracy(self, X, T):
        """计算分类准确率"""
        Y = self.predict(X, train_flg=False)  # 测试模式
        Y = np.argmax(Y, axis=1)  # 获取预测类别
        # 处理one-hot编码的标签
        if T.ndim != 1 : T = np.argmax(T, axis=1)

        accuracy = np.sum(Y == T) / float(X.shape[0])
        return accuracy

    def numerical_gradient(self, X, T):
        """数值微分法计算梯度"""
        loss_W = lambda W: self.loss(X, T, train_flg=True)  # 定义损失函数

        grads = {}
        # 计算各层权重和偏置的梯度
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = numerical_gradient(loss_W, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_W, self.params['b' + str(idx)])

            # 计算BatchNorm层的gamma和beta梯度
            if self.use_batchnorm and idx != self.hidden_layer_num+1:
                grads['gamma' + str(idx)] = numerical_gradient(loss_W, self.params['gamma' + str(idx)])
                grads['beta' + str(idx)] = numerical_gradient(loss_W, self.params['beta' + str(idx)])

        return grads

    def gradient(self, x, t):
        """反向传播法计算梯度"""
        # 前向传播
        self.loss(x, t, train_flg=True)

        # 反向传播
        dout = 1  # 初始化梯度
        dout = self.last_layer.backward(dout)

        # 各层反向传播
        layers = list(self.layers.values())
        layers.reverse()  # 反转层顺序
        for layer in layers:
            dout = layer.backward(dout)

        # 计算并存储梯度(含L2正则化项)
        grads = {}
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = (self.layers['Affine' + str(idx)].dW +
                                   self.weight_decay_lambda * self.params['W' + str(idx)])
            grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db

            # 计算BatchNorm层的gamma和beta梯度
            if self.use_batchnorm and idx != self.hidden_layer_num+1:
                grads['gamma' + str(idx)] = self.layers['BatchNorm' + str(idx)].dgamma
                grads['beta' + str(idx)] = self.layers['BatchNorm' + str(idx)].dbeta

        return grads

"""
使用示例：
1. 初始化网络(带BatchNorm和Dropout):
   net = MultiLayerNetExtend(input_size=784, hidden_size_list=[100,100],
                           output_size=10, use_batchnorm=True, use_dropout=True)

2. 前向传播:
   y = net.predict(x, train_flg=True)  # 训练模式
   y = net.predict(x, train_flg=False) # 测试模式

3. 计算损失:
   loss = net.loss(x, t, train_flg=True)

4. 计算准确率:
   acc = net.accuracy(X, T)

5. 计算梯度:
   grads = net.gradient(x, t)  # 反向传播法
   grads = net.numerical_gradient(X, T)  # 数值微分法(用于验证)

注意事项：
1. BatchNorm和Dropout在训练和测试时的行为不同
2. 数值梯度计算较慢，仅用于调试
3. 权重衰减(L2正则化)可防止过拟合
4. 可根据需要组合使用各种正则化技术
"""



"""
这段代码实现了一个功能丰富的全连接神经网络，支持多种正则化和优化技术。以下是对其的分析和改进建议：

主要优点：
1. 模块化设计：使用OrderedDict管理网络层，结构清晰，便于扩展。

2. 功能完备：整合了L2正则化、Dropout、BatchNorm等现代深度学习技术。

3. 初始化灵活：支持He/Xavier初始化，适配不同激活函数。

4. 双梯度计算：同时提供数值梯度和反向传播实现，便于梯度验证。

5. 模式区分：通过train_flg参数区分训练/测试模式，正确处理Dropout和BatchNorm的不同行为。

潜在问题及改进建议：
1. 默认模式风险：

问题：loss()方法默认train_flg=False，可能导致训练时损失计算不准确
建议：将loss方法签名改为def loss(self, x, t, train_flg=True)
2. 正则化范围：

问题：输出层权重也被L2正则化，可能影响最终分类效果
建议：添加参数控制是否正则化输出层
3. 激活函数扩展：

问题：硬编码sigmoid/relu，扩展新激活函数需要修改代码
建议：使用注册机制或字典扩展激活函数
4. 数值梯度效率：

问题：numerical_gradient逐个参数计算，效率低下
建议：添加数值梯度批量计算模式
5. 初始化方法匹配：

问题：当激活函数与初始化方法不匹配时没有警告
建议：添加类型检查警告机制
使用建议示例：
下面是AI助手生成的
"""
# 初始化网络（包含安全检查）
try:
    net = MultiLayerNetExtend(
        input_size=784,
        hidden_size_list=[200, 200],
        output_size=10,
        activation='relu',
        weight_init_std='he',
        use_batchnorm=True,
        use_dropout=True
    )
except KeyError as e:
    print(f"参数错误: {e}")

# 训练循环
for epoch in range(max_epoch):
    # 前向传播（训练模式）
    loss = net.loss(x_batch, t_batch, train_flg=True)

    # 反向传播
    grads = net.gradient(x_batch, t_batch)

    # 参数更新（需实现优化器）
    optimizer.update(net.params, grads)

    # 验证时使用测试模式
    if epoch % 10 == 0:
        val_acc = net.accuracy(x_val, t_val)
        print(f"Epoch {epoch}, Val Acc: {val_acc:.4f}")